我們取得了使用者輸入的請求之後,再來就是如何將使用者的請求導向正確的位址。
在 Laravel 11 裡面,首先我們會先看到 bootstrap/app.php 裡面有著
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: [
            __DIR__.'/../routes/web.php',
        ],
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();
這裡我們先關注跟路由相關的部分,configure() 和 withMiddleware() 之類的邏輯之後有機會再來處理。
在這邊使用 withRouting() 引入了 routes/web.php 之後,在這個檔案裡面定義路由。
Laravel 是怎麼實作這段邏輯的呢?我們一起來看看。
首先我們看看 withRouting() 的實作
    public function withRouting(?Closure $using = null,
        array|string|null $web = null,
        array|string|null $api = null,
        ?string $commands = null,
        ?string $channels = null,
        ?string $pages = null,
        ?string $health = null,
        string $apiPrefix = 'api',
        ?callable $then = null)
    {
        if (is_null($using) && (is_string($web) || is_array($web) || is_string($api) || is_array($api) || is_string($pages) || is_string($health)) || is_callable($then)) {
            $using = $this->buildRoutingCallback($web, $api, $pages, $health, $apiPrefix, $then);
        }
        AppRouteServiceProvider::loadRoutesUsing($using);
        $this->app->booting(function () {
            $this->app->register(AppRouteServiceProvider::class, force: true);
        });
        if (is_string($commands) && realpath($commands) !== false) {
            $this->withCommands([$commands]);
        }
        if (is_string($channels) && realpath($channels) !== false) {
            $this->withBroadcasting($channels);
        }
        return $this;
    }
這邊我們的 web 提供的是一個陣列,所以會進到 buildRoutingCallback()。
buildRoutingCallback() 的實作則是
    protected function buildRoutingCallback(array|string|null $web,
        array|string|null $api,
        ?string $pages,
        ?string $health,
        string $apiPrefix,
        ?callable $then)
    {
        return function () use ($web, $api, $pages, $health, $apiPrefix, $then) {
            if (is_string($api) || is_array($api)) {
                if (is_array($api)) {
                    foreach ($api as $apiRoute) {
                        if (realpath($apiRoute) !== false) {
                            Route::middleware('api')->prefix($apiPrefix)->group($apiRoute);
                        }
                    }
                } else {
                    Route::middleware('api')->prefix($apiPrefix)->group($api);
                }
            }
            if (is_string($health)) {
                Route::get($health, function () {
                    Event::dispatch(new DiagnosingHealth);
                    return View::file(__DIR__.'/../resources/health-up.blade.php');
                });
            }
            if (is_string($web) || is_array($web)) {
                if (is_array($web)) {
                    foreach ($web as $webRoute) {
                        if (realpath($webRoute) !== false) {
                            Route::middleware('web')->group($webRoute);
                        }
                    }
                } else {
                    Route::middleware('web')->group($web);
                }
            }
            if (is_string($pages) &&
                realpath($pages) !== false &&
                class_exists(Folio::class)) {
                Folio::route($pages, middleware: $this->pageMiddleware);
            }
            if (is_callable($then)) {
                $then($this->app);
            }
        };
    }
這邊有關 $web 的邏輯是這段
if (is_string($web) || is_array($web)) {
	if (is_array($web)) {
		foreach ($web as $webRoute) {
			if (realpath($webRoute) !== false) {
				Route::middleware('web')->group($webRoute);
			}
		}
	} else {
		Route::middleware('web')->group($web);
	}
}
可以看到,這一段的邏輯是如果輸入是一個陣列,就依次執行
Route::middleware('web')->group($webRoute);
也就是說,最終註冊路由的地方,還是使用 Route::group() 這個函數
我們繼續看 group() 的實作
/**
 * Create a route group with shared attributes.
 *
 * @param  array  $attributes
 * @param  \Closure|array|string  $routes
 * @return $this
 */
public function group(array $attributes, $routes)
{
	foreach (Arr::wrap($routes) as $groupRoutes) {
		$this->updateGroupStack($attributes);
		// Once we have updated the group stack, we'll load the provided routes and
		// merge in the group's attributes when the routes are created. After we
		// have created the routes, we will pop the attributes off the stack.
		$this->loadRoutes($groupRoutes);
		array_pop($this->groupStack);
	}
	return $this;
}
這邊的註解告訴我們,更新了 group stack 之後,就會使用 loadRoutes() 來加入新的路由
loadRoutes() 的實作如下:
/**
 * Load the provided routes.
 *
 * @param  \Closure|string  $routes
 * @return void
 */
protected function loadRoutes($routes)
{
	if ($routes instanceof Closure) {
		$routes($this);
	} else {
		(new RouteFileRegistrar($this))->register($routes);
	}
}
由於我們前面宣告的 $web 是包含許多檔名的一個陣列,在這邊我們會進入到 (new RouteFileRegistrar($this))->register($routes); 的邏輯
這段的實作非常簡單,只有短短幾行
/**
 * Require the given routes file.
 *
 * @param  string  $routes
 * @return void
 */
public function register($routes)
{
	$router = $this->router;
	require $routes;
}
到這邊我們就可以知道,Laravel 在最後,是依次根據陣列內的檔名來 require 檔案,引入 $web 內列出的所有路由檔案。
今天我們就先看到這邊,明天我們再來看看 Laravel 是怎麼定義路由與解析路由的。